
//
// 130105 - AH - Created.
//

package org.japril.jutil;

import java.io.IOException;

import javax.xml.parsers.ParserConfigurationException;

import org.xml.sax.Attributes;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

/**
 * <p>
 *   Utility methods of use when parsing XML with the SAX API.
 * </p>
 * @author Arne Halvorsen (AH)
 */
public final class SaxUtil
{
  // Utility pattern dictates private constructor.
  private SaxUtil(){}
 
  /**
   * <p>
   *   Gets mandatory attribute.
   * </p>
   * @param name  Attribute name.
   * @param attrs {@link Attributes} to fetch from.
   * @param loc   {@link Locator Location} at, may be {@code null}.
   * @return Value.
   * @throws SAXException If attribute not found.
   */
  public static String getMandatoryAttr(String name, Attributes attrs, 
    Locator loc) throws SAXException
  {
    if (name==null)
    {
      throw new NullPointerException("name");
    }
    if (attrs==null)
    {
      throw new NullPointerException("attrs");
    }
   
    String retVal=attrs.getValue(name);
    if (retVal==null)
    {
      throw new SAXParseException("Missing attribute: "+name, loc);
    }
   
    return retVal;
  }
 
  /**
   * <p>
   *   Gets attribute.
   * </p>
   * @param name  Name of attribute.
   * @param def   Default value.
   * @param attrs {@link Attributes} to fetch from.
   * @return Value.
   */
  public static String getAttr(String name, String def, Attributes attrs)
  {
    String s=attrs.getValue(name);
    return (s==null) ? def : s;
  }
 
  /**
   * <p>
   *   Gets {@code boolean} attribute.
   * </p>
   * @param name  Attribute name.
   * @param def   Default value.
   * @param attrs {@link Attributes} to fetch from.
   * @param loc   {@link Locator Location} at, may be {@code null}.
   * @return Value.
   * @throws SAXException If attribute not an {@code boolean}.
   */
  public static boolean getBooleanAttr(String name, boolean def, 
    Attributes attrs, Locator loc) throws SAXException
  {
    String s=attrs.getValue(name);
    if (s==null) return def;
    if (s.equals("true")) return true;
    if (s.equals("false")) return false;
    throw new SAXParseException("Attribute: "+name+" not a boolean ("+s+")", 
      loc);
  }
 
  /**
   * <p>
   *   Gets mandatory {@code boolean} attribute.
   * </p>
   * @param name  Attribute name.
   * @param attrs {@link Attributes} to fetch from.
   * @param loc   {@link Locator Location} at, may be {@code null}.
   * @return Value.
   * @throws SAXException If attribute not found or attribute not an 
   *         {@code boolean}.
   */
  public static boolean getMandatoryBooleanAttr(String name, Attributes attrs, 
    Locator loc) throws SAXException
  {
    String s=getMandatoryAttr(name, attrs, loc);
    if (s.equals("true")) return true;
    if (s.equals("false")) return false;
    throw new SAXParseException("Attribute: "+name+" not a boolean ("+s+")", 
      loc);
  }
 
  /**
   * <p>
   *   Gets 
   *   {@link Boolean} attribute.
   * </p>
   * @param name  Attribute name.
   * @param attrs {@link Attributes} to fetch from.
   * @param loc   {@link Locator Location} at, may be {@code null}.
   * @return Value or {@code null} if not found.
   * @throws SAXException If attribute not an {@code boolean}.
   */
  public static Boolean getBooleanAttr(String name, Attributes attrs, 
    Locator loc) throws SAXException
  {
    String s=attrs.getValue(name);
    if (s==null) return null;
    if (s.equals("true")) return Boolean.TRUE;
    if (s.equals("false")) return Boolean.FALSE;
    throw new SAXParseException("Attribute: "+name+" not a boolean ("+s+")", 
      loc);
  }
 
  /**
   * <p>
   *   Gets mandatory {@code int} attribute.
   * </p>
   * @param name  Attribute name.
   * @param attrs {@link Attributes} to fetch from.
   * @param loc   {@link Locator Location} at, may be {@code null}.
   * @return Value.
   * @throws SAXException If attribute not found or attribute not an
   *         {@code int}.
   */
  public static int getMandatoryIntAttr(String name, Attributes attrs,
    Locator loc) throws SAXException
  {
    String s=getMandatoryAttr(name, attrs, loc);
    try
    {
      return Integer.parseInt(s);
    }
    catch (NumberFormatException nfx)
    {
      throw new SAXParseException("Attribute: "+name+" not an int ("+s+")", 
        loc);
    }
  }
 
  /**
   * <p>
   *   Gets {@code int} attribute.
   * </p>
   * @param name  Attribute name.
   * @param def   Default value.
   * @param attrs {@link Attributes} to fetch from.
   * @param loc   {@link Locator Location} at, may be {@code null}.
   * @return Value.
   * @throws SAXException If attribute not an {@code int}.
   */
  public static int getIntAttr(String name, int def, Attributes attrs,
    Locator loc) throws SAXException
  {
    String s=attrs.getValue(name);
    if (s==null) return def;
    try
    {
      return Integer.parseInt(s);
    }
    catch (NumberFormatException nfx)
    {
      throw new SAXParseException("Attribute: "+name+" not an int ("+s+")", 
        loc);
    }
  }
 
  /**
   * <p>
   *   Gets 
   *   {@link Integer} attribute.
   * </p>
   * @param name  Attribute name.
   * @param attrs {@link Attributes} to fetch from.
   * @param loc   {@link Locator Location} at, may be {@code null}.
   * @return Value or {@code null} if not found.
   * @throws SAXException If attribute not an {@code int}.
   */
  public static Integer getIntegerAttr(String name, Attributes attrs, 
    Locator loc) throws SAXException
  {
    String s=attrs.getValue(name);
    if (s==null) return null;
    try
    {
      return Integer.parseInt(s);
    }
    catch (NumberFormatException nfx)
    {
      throw new SAXParseException("Attribute: "+name+" not an int ("+s+")", 
        loc);
    }
  }
 
  /**
   * <p>
   *   Gets mandatory {@code long} attribute.
   * </p>
   * @param name  Attribute name.
   * @param attrs {@link Attributes} to fetch from.
   * @param loc   {@link Locator Location} at, may be {@code null}.
   * @return Value.
   * @throws SAXException If attribute not found or attribute not an
   *         {@code long}.
   */
  public static long getMandatoryLongAttr(String name, Attributes attrs,
    Locator loc) throws SAXException
  {
    String s=getMandatoryAttr(name, attrs, loc);
    try
    {
      return Long.parseLong(s);
    }
    catch (NumberFormatException nfx)
    {
      throw new SAXParseException("Attribute: "+name+" not an long ("+s+")", 
        loc);
    }
  }
 
  /**
   * <p>
   *   Gets {@code long} attribute.
   * </p>
   * @param name  Attribute name.
   * @param def   Default value.
   * @param attrs {@link Attributes} to fetch from.
   * @param loc   {@link Locator Location} at, may be {@code null}.
   * @return Value.
   * @throws SAXException If attribute not an {@code long}.
   */
 public static long getLongAttr(String name, long def, Attributes attrs,
   Locator loc) throws SAXException
 {
   String s=attrs.getValue(name);
   if (s==null) return def;
   try
   {
     return Long.parseLong(s);
   }
   catch (NumberFormatException nfx)
   {
     throw new SAXParseException("Attribute: "+name+" not an long ("+s+")", 
       loc);
   }
 }
 
  /**
   * <p>
   *   Gets 
   *   {@link Long} attribute.
   * </p>
   * @param name  Attribute name.
   * @param attrs {@link Attributes} to fetch from.
   * @param loc   {@link Locator Location} at, may be {@code null}.
   * @return Value or {@code null} if not found.
   * @throws SAXException If attribute not an {@code long}.
   */
  public static Long getLongAttr(String name, Attributes attrs, Locator loc) 
    throws SAXException
  {
    String s=attrs.getValue(name);
    if (s==null) return null;
    try
    {
      return Long.parseLong(s);
    }
    catch (NumberFormatException nfx)
    {
      throw new SAXParseException("Attribute: "+name+" not an long ("+s+")", 
        loc);
    }
  }
 
  /**
   * <p>
   *   Gets {@code float} attribute.
   * </p>
   * @param name  Attribute name.
   * @param def   Default value.
   * @param attrs {@link Attributes} to fetch from.
   * @param loc   {@link Locator Location} at, may be {@code null}.
   * @return Value.
   * @throws SAXException If attribute not an {@code float}.
   */
  public static float getFloatAttr(String name, float def, Attributes attrs,
    Locator loc) throws SAXException
  {
    String s=attrs.getValue(name);
    if (s==null) return def;
    try
    {
      return Float.parseFloat(s);
    }
    catch (NumberFormatException nfx)
    {
      throw new SAXParseException("Attribute: "+name+" not a float ("+s+")", 
        loc);
    }
  }
 
  /**
   * <p>
   *   Gets mandatory {@code float} attribute.
   * </p>
   * @param name  Attribute name.
   * @param attrs {@link Attributes} to fetch from.
   * @param loc   {@link Locator Location} at, may be {@code null}.
   * @return Value.
   * @throws SAXException If attribute not found or attribute not an
   *         {@code float}.
   */
  public static float getMandatoryFloatAttr(String name, Attributes attrs,
    Locator loc) throws SAXException
  {
    String s=getMandatoryAttr(name, attrs, loc);
    try
    {
      return Float.parseFloat(s);
    }
    catch (NumberFormatException nfx)
    {
      throw new SAXParseException("Attribute: "+name+" not a float ("+s+")", 
        loc);
    }
  }
 
  /**
   * <p>
   *   Gets
   *   {@link Float} attribute.
   * </p>
   * @param name  Attribute name.
   * @param def   Default value.
   * @param attrs {@link Attributes} to fetch from.
   * @param loc   {@link Locator Location} at, may be {@code null}.
   * @return Value or {@code null} if not found.
   * @throws SAXException If attribute not an {@code float}.
   */
  public static Float getFloatAttr(String name, Attributes attrs, Locator loc) 
    throws SAXException
  {
    String s=attrs.getValue(name);
    if (s==null) return null;
    try
    {
      return Float.parseFloat(s);
    }
    catch (NumberFormatException nfx)
    {
      throw new SAXParseException("Attribute: "+name+" not a float ("+s+")", 
        loc);
    }
  }
 
  /**
   * <p>
   *   Gets mandatory {@code double} attribute.
   * </p>
   * @param name  Attribute name.
   * @param attrs {@link Attributes} to fetch from.
   * @param loc   {@link Locator Location} at, may be {@code null}.
   * @return Value.
   * @throws SAXException If attribute not found or attribute not an
   *         {@code double}.
   */
  public static double getMandatoryDoubleAttr(String name, Attributes attrs,
    Locator loc) throws SAXException
  {
    String s=getMandatoryAttr(name, attrs, loc);
    try
    {
      return Double.parseDouble(s);
    }
    catch (NumberFormatException nfx)
    {
      throw new SAXParseException("Attribute: "+name+" not a double ("+s+")", 
        loc);
    }
  }
 
  /**
   * <p>
   *   Gets {@code double} attribute.
   * </p>
   * @param name  Attribute name.
   * @param def   Default value.
   * @param attrs {@link Attributes} to fetch from.
   * @param loc   {@link Locator Location} at, may be {@code null}.
   * @return Value.
   * @throws SAXException If attribute not an {@code double}.
   */
  public static double getDoubleAttr(String name, double def, Attributes attrs,
    Locator loc) throws SAXException
  {
    String s=attrs.getValue(name);
    if (s==null) return def;
    try
    {
      return Double.parseDouble(s);
    }
    catch (NumberFormatException nfx)
    {
      throw new SAXParseException("Attribute: "+name+" not a double ("+s+")", 
        loc);
    }
  }
 
  /**
   * <p>
   *   Gets 
   *   {@link Double} attribute.
   * </p>
   * @param name  Attribute name.
   * @param attrs {@link Attributes} to fetch from.
   * @param loc   {@link Locator Location} at, may be {@code null}.
   * @return Value or {@code null} if not found.
   * @throws SAXException If attribute not an {@code double}.
   */
  public static Double getDoubleAttr(String name, Attributes attrs, Locator loc)
    throws SAXException
  {
    String s=attrs.getValue(name);
    if (s==null) return null;
    try
    {
      return Double.parseDouble(s);
    }
    catch (NumberFormatException nfx)
    {
      throw new SAXParseException("Attribute: "+name+" not a double ("+s+")", 
        loc);
    }
  }
 
  /**
   * <p>
   *   Throws an
   *   {@link SAXException} with error message stating that named element is not
   *   allowed 'here' if a reference is {@code null}. 
   * </p>
   * @param element Name on element in question.
   * @param o       Reference in question.
   * @param loc     {@link Locator Location} at, may be {@code null}.
   * @throws SAXException If {@code o==null}.
   * @see #assumeNotNull(String, Object, Locator) 
   */
  public static void assumeNotNull(String element, Object o, Locator loc) 
    throws SAXException
  {
    if (element==null)
    {
      throw new NullPointerException("element");
    }    
    if (o==null) 
    {
      throw new SAXParseException("Element: "+element+" not allowed here", loc);
    }
  }
 
  /**
   * <p>
   *   Throws an
   *   {@link SAXException} with error message stating that named element is not
   *   allowed 'here' if a reference is <i>not</i> {@code null}. 
   * </p>
   * @param element Name on element in question.
   * @param o       Reference in question.
   * @param loc     {@link Locator Location} at, may be {@code null}.
   * @throws SAXException If {@code o!=null}.
   * @see #assumeNotNull(String, Object, Locator)
   */
  public static void assumeNull(String element, Object o, Locator loc) 
    throws SAXException
  {
    if (element==null)
    {
      throw new NullPointerException("element");
    }     
    if (o!=null) 
    {
      throw new SAXParseException("Element: "+element+" not allowed here", loc);
    }
  }
 
  /**
   * <p>
   *   Translates a
   *   {@link ParserConfigurationException} to an
   *   {@link IOException}.
   * </p>
   * @param pcx Exception to translate.    
   * @return Translated.
   * @see #getIOException(SAXException) 
   * @see #getIOException(SAXParseException)
   */
  public static IOException getIOException(ParserConfigurationException pcx)
  {
    if (pcx==null)
    {
      throw new NullPointerException("pcx");
    }
   
    IOException iox=new IOException(getMessage(pcx));
    iox.initCause(pcx);
    return iox;
  }
 
  /**
   * <p>
   *   Translates a
   *   {@link SAXException} to an
   *   {@link IOException}.
   * </p>
   * <p>
   *   If {@code saxx} is an
   *   {@link SAXParseException} forwards to
   *   {@link #getIOException(SAXParseException)}.
   * </p>
   * @param saxx Exception to translate.    
   * @return Translated.
   * @see #getIOException(ParserConfigurationException) 
   * @see #getIOException(SAXParseException)
   */
  public static IOException getIOException(SAXException saxx)     
  {
    if (saxx==null)
    {
      throw new NullPointerException("saxx");
    }
   
    if (saxx instanceof SAXParseException)
    {
      return getIOException((SAXParseException)saxx);
    }
   
    IOException iox=new IOException(getMessage(saxx));
    iox.initCause(saxx);
    return iox;
  }
 
  /**
   * <p>
   *   Translates a
   *   {@link SAXParseException} to an
   *   {@link IOException} with a message containing line number and column
   *   number if {@code saxpx} has such info.
   * </p>
   * @param saxpx Exception to translate.
   * @return Translated.   
   * @see #getIOException(ParserConfigurationException) 
   * @see #getIOException(SAXException)
   */
  public static IOException getIOException(SAXParseException saxpx)     
  {
    if (saxpx==null)
    {
      throw new NullPointerException("saxpx");
    }
   
    StringBuffer sb=new StringBuffer();
    int line=saxpx.getLineNumber();
    if (line!=-1)
    {
      sb.append("Line ").append(line);
      int col=saxpx.getColumnNumber();
      if (col!=-1)
      {
        sb.append(", column ").append(col).append(" : ");
      }
    }
   
    sb.append(getMessage(saxpx));
    IOException iox=new IOException(sb.toString());
    iox.initCause(saxpx);
    return iox;
  }
 
  /**
   * <p>
   *   Translates an
   *   {@link Exception} to an
   *   {@link SAXXException}.
   * </p>
   * @param x   Exception to translate.
   * @param loc {@link Locator}, may be {@code null}.    
   * @return Translated.
   */
  public static SAXException getSAXException(Exception x, Locator loc)
  {
    if (x==null)
    {
      throw new NullPointerException("x");
    }
   
    String msg=getMessage(x);
    SAXException retVal=new SAXParseException(msg, loc);
    retVal.initCause(x);
    return retVal;
  }
  
  /**
   * <p>
   *   Gets message for exception that never is {@code null} or the empty
   *   {@code String}.
   * </p>
   * <p>
   * @param t {@link Throwable} to get message for.
   * @return If
   *         {@code x.getMessage()!=null && x.getMessage().trim().length()&gt;0}
   *         returns {@code x.getMessage()} else returns
   *         {@code x.getClass().getName()}.
   */
  private static String getMessage(Throwable t)
  {
    if (t==null)
    {
      throw new NullPointerException("t");
    }

    String msg=t.getMessage();
    msg=(msg==null) ? t.getClass().getName() : msg.trim();
    return (msg.length()==0) ? t.getClass().getName() : msg;
  }
 
}
